home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / pcl / cl-nd-cl.lha / clue / clio / slider.lisp < prev    next >
Text File  |  1990-07-19  |  46KB  |  1,115 lines

  1. ;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Fonts:(CPTFONT); Syntax:Common-Lisp;  -*-
  2.  
  3.  
  4. ;;;----------------------------------------------------------------------------------+
  5. ;;;                                                                                  |
  6. ;;;                          TEXAS INSTRUMENTS INCORPORATED                          |
  7. ;;;                                  P.O. BOX 149149                                 |
  8. ;;;                                AUSTIN, TEXAS 78714                               |
  9. ;;;                                                                                  |
  10. ;;;             Copyright (C) 1989, 1990 Texas Instruments Incorporated.             |
  11. ;;;                                                                                  |
  12. ;;; Permission is granted to any individual or institution to use, copy, modify, and |
  13. ;;; distribute this software, provided that  this complete copyright and  permission |
  14. ;;; notice is maintained, intact, in all copies and supporting documentation.        |
  15. ;;;                                                                                  |
  16. ;;; Texas Instruments Incorporated provides this software "as is" without express or |
  17. ;;; implied warranty.                                                                |
  18. ;;;                                                                                  |
  19. ;;;----------------------------------------------------------------------------------+
  20.  
  21.  
  22. (in-package "CLIO-OPEN")
  23.  
  24. (export '(
  25.       make-slider
  26.       slider
  27.       )
  28.     'clio-open)
  29.  
  30. (defmacro translate-x-to-y (x x-width slider)
  31.   "Translate x coord for horizontal slider into y of a vertical slider.
  32. X-WIDTH is the width in x-direction that must be changed into a y-offset."
  33.   `(with-slots (height) ,slider
  34.      (- height 1 ,x (max 0 (1- ,x-width)))))
  35.  
  36.  
  37. (defmacro confine-to (value minimum maximum)
  38.   `(max ,minimum (min ,value ,maximum)))
  39.  
  40.  
  41. (defmacro align (value increment)
  42.   ;; Since we are talking SCALE VALUE here
  43.   ;; we pixel-round since it may be a REAL number.
  44.   `(if (= 1 ,increment)
  45.        ,value
  46.        (* (pixel-round ,value ,increment) ,increment)))
  47.  
  48.  
  49. (defmacro value-length (value minimum)
  50.   `(- ,value ,minimum))
  51.  
  52. ;;;----------------------------------------------------------------------------+
  53. ;;;                                                                            |
  54. ;;;                             Slider                                         |
  55. ;;;                                                                            |
  56. ;;;----------------------------------------------------------------------------+
  57.  
  58.  
  59. ;; Implementation Strategy:
  60. ;;
  61. ;;  Since CLIO should implement a "look and
  62. ;; feel" independent implemementation of open-look, then only those
  63. ;; parts of the slider that are going to exist in most implemementations
  64. ;; will be supported.  Since make-slider accepts :
  65. ;; (increment indicator-size maximum minimum orientation update-delay value compress-exposures)
  66. ;; then all "features" must be derived from these inputs.
  67. ;; 
  68. ;;  To provide numeric visual feedback of the current value is desirable,
  69. ;; but providing this as a typein field or read-only field really requires a label
  70. ;; or else the displayed result is somewhat confusing.
  71. ;;  The current value will be implemented as AUTOMATIC tick-marks and tick-mark
  72. ;; labels based on the min-max values and the space available to print them. The
  73. ;; actual current value will not be printed but will be discernable by "reading the scale".
  74. ;; 
  75. ;; Thus the slider parts implemented are :
  76. ;;     (bar, drag-box, (automatic) tick-marks, (automatic) tick-text)
  77. ;; and the following will NOT be provided :
  78. ;;      (End boxes, labels, typein fields, non-numeric text of any kind)
  79. ;; This means that the read-only min-max current-value fields will be provided only by way
  80. ;; of the min-/max tick-mark tick-text labels.
  81. ;; 
  82. ;;  When horizontal sliders require max (or min) values of more than 2 digits
  83. ;; then the tick-mark & tick-mark-number-labels are difficult to display. In this
  84. ;; case a :vertical orientation is more appropriate. If more than 2-digits are used
  85. ;; for a :horizontal slider then the tick-mark granularity will be reduced in order
  86. ;; to accommodate the width of the digits.
  87. ;; 
  88.  
  89. (defcontact slider (core contact)
  90.   ((increment     :type         number
  91.          :reader        scale-increment        ;; SETF method defined below
  92.         :initarg    :increment
  93.          :initform     1)
  94.    
  95.    (indicator-size        ;; The size of the distance between tick-marks in value units.
  96.                                 ;; :off means "no tickmarks or tick labels", 1 will cause tick-mark
  97.                     ;; overlap if there is not enough space to display. [2..N] will
  98.                 ;; cause a tick-spacing of [1..(1- N)]. 
  99.                 ;; 
  100.                 :type         (or number (member :off)) ;; 0 means "automatic" tick mark spacing.
  101.         :reader        scale-indicator-size    ;; SETF method defined below
  102.         :initarg    :indicator-size
  103.         :initform     0)
  104.    
  105.    (maximum     :type         number
  106.          :reader        scale-maximum        ;; SETF method defined below
  107.         :initarg    :maximum
  108.          :initform     1)
  109.    
  110.    (minimum     :type         number
  111.          :reader        scale-minimum        ;; SETF method defined below
  112.         :initarg    :minimum
  113.          :initform     0)
  114.    
  115.    (orientation :type         (member :horizontal :vertical)
  116.          :reader        scale-orientation    ;; SETF method defined below
  117.         :initarg    :orientation
  118.          :initform     :horizontal)
  119.    
  120.    (update-delay :type        (or number (member :until-done))
  121.          :reader    scale-update-delay    ;; SETF method defined below
  122.          :initarg    :update-delay
  123.          :initform    0)
  124.    
  125.    (value     :type         number
  126.          :reader        scale-value    ;; SETF method defined below
  127.         :initarg    :value
  128.          :initform     0)
  129.    
  130.    (compress-exposures 
  131.                 :initform       :on
  132.         :type           (member :off :on)
  133.         :reader         contact-compress-exposures
  134.         :allocation     :class)
  135.  
  136.    ;; Internal storage slots
  137.    (font        :type    fontable);; font for current scale
  138.  
  139.    (min-text-width :type    number)     ;; pixel width of minimum value print string
  140.  
  141.    (max-text-width :type    number)     ;; pixel width of maximum value print string
  142.  
  143.    (dimensions       :type    list)    ;; (getf *slider-dimensions* scale)
  144.  
  145.    (middle-length  :type        number)     ;; pixel length between first & last tick marks
  146.    )
  147.   
  148.   (:resources
  149.     increment indicator-size maximum minimum orientation update-delay value 
  150.     (border-width :initform 0)
  151.     (event-mask   :initform #.(make-event-mask :exposure :pointer-motion-hint))))
  152.  
  153.  
  154. ;;;----------------------------------------------------------------------------+
  155. ;;;                                                                            |
  156. ;;; Setf Accessors                                                             |
  157. ;;;                                                                            |
  158. ;;;----------------------------------------------------------------------------+
  159.  
  160. (defmethod (setf scale-orientation) (new-orientation (slider slider))
  161.   (with-slots (orientation width height) slider
  162.     (unless (eq orientation new-orientation)
  163.       (check-type new-orientation (member :horizontal :vertical))
  164.       
  165.       (setf orientation new-orientation)
  166.       
  167.       (multiple-value-bind (new-width new-height)
  168.       (preferred-size slider :width height :height width)
  169.     (change-geometry slider :width new-width :height new-height :accept-p t))))
  170.   
  171.   new-orientation)
  172.  
  173. (defmethod (setf scale-update-delay) (new-update-delay (slider slider))
  174.   (with-slots (update-delay) slider
  175.     (assert (or (eq new-update-delay :until-done)
  176.         (and (numberp new-update-delay) (not (minusp new-update-delay)))) (new-update-delay)
  177.         "~a is neither :UNTIL-DONE or a non-negative number." new-update-delay)    
  178.     (setf update-delay new-update-delay)))
  179.  
  180. (defmethod (setf scale-value) (new-value (slider slider))
  181.   (scale-update slider :value new-value)
  182.   new-value) 
  183.     
  184. (defmethod (setf scale-minimum) (new-minimum (slider slider))
  185.   (scale-update slider :minimum new-minimum)
  186.   new-minimum)
  187.  
  188. (defmethod (setf scale-maximum) (new-maximum (slider slider))
  189.   (scale-update slider :maximum new-maximum)
  190.   new-maximum)
  191.  
  192. (defmethod (setf scale-increment) (new-increment (slider slider))
  193.   (scale-update slider :increment new-increment)
  194.   new-increment)
  195.  
  196. (defmethod (setf scale-indicator-size) (new-indicator-size (slider slider))
  197.   (scale-update slider :indicator-size new-indicator-size)
  198.   new-indicator-size)
  199.  
  200.  
  201. ;;;------------------------------------------------------------------------------------+
  202. ;;;                                                                                    |
  203. ;;;      Helper Functions                                                             |
  204. ;;;                                                                                    |
  205. ;;;------------------------------------------------------------------------------------+
  206.  
  207. (defun slider-tick-mark-thickness (slider)
  208.   (if (eq :extra-large (contact-scale slider))
  209.       3
  210.       2))
  211.  
  212. (defun slider-bar-tick-gap (slider)
  213.   ;; Distance top of tick-mark and nearest point on bar
  214.   (1+ (case (contact-scale slider)
  215.     (:small 1) (:medium 2) (:large 3) (:extra-large 4))))
  216.  
  217. (defun slider-margin (slider margin)
  218.   "Returns the MARGIN of SLIDER, one of :min :top :text :max"
  219.   ;; This is initially *slider-default-margin* until
  220.   ;; after the PREFERRED-SIZE method is called. Then margins include
  221.   ;; any additional increase due to a width or height larger than the
  222.   ;; preferred size. :LEFT means the left margin for this particular orientation.
  223.   (assert (member margin '(:min :top :text :max)) (margin)
  224.       "~a is an illegal margin" margin)
  225.     (let ((margins (getf (getf (window-plist slider) :slider-info) :margins)))
  226.       (or (getf margins margin)
  227.       ;;  Calling before margins are setup is never
  228.       ;;  done but code is here for completeness
  229.       *slider-default-margin*)))
  230.  
  231. (defun first-tick-offset (slider)
  232.   ;; Offset, not including (slider-margin slider :min), from
  233.   ;; min edge of contact to CENTERLINE of first tick-mark.
  234.   (with-slots (min-text-width orientation font dimensions indicator-size) slider
  235.     (let ((tick-mark-offset (slidebar-tick-mark-offset dimensions))
  236.        (gap (slidebar-gap dimensions)))
  237.       (+ *slider-default-margin*
  238.      ;;  Add GAP below since drag-box clear-gap-around extends past bar edge
  239.      (if (eq :off indicator-size)
  240.          (+ gap tick-mark-offset)
  241.          (if (eq orientation :horizontal)
  242.          (max (floor min-text-width 2) (+ gap tick-mark-offset))
  243.          (+ gap tick-mark-offset) ;; the text baseline is ALWAYS above end of bar MIN
  244.          ))))))
  245.  
  246. (defun last-tick-offset (slider)
  247.   ;; Offset, not including (slider-margin slider :max), from
  248.   ;; max edge of contact to CENTERLINE of last tick-mark.
  249.   (with-slots (max-text-width orientation font dimensions indicator-size) slider
  250.     (let ((tick-mark-offset (slidebar-tick-mark-offset dimensions))
  251.       (gap (slidebar-gap dimensions)))
  252.       (+ *slider-default-margin*
  253.      ;;  Add GAP below since drag-box clear-gap-around extends past bar edge
  254.      (if (eq :off indicator-size)
  255.          (+ gap tick-mark-offset)
  256.          (if (eq orientation :horizontal)
  257.          (max (ceiling max-text-width 2) (+ gap tick-mark-offset))
  258.          (+ tick-mark-offset
  259.             (max gap 
  260.              ;; font-ascent may go beyond end of bar MAX if font is bigger than "point" requested
  261.              (abs (- (cadr (getf (slidebar-bar-text-offset dimensions) orientation))
  262.                  (max-char-ascent font)
  263.                  ))))))))))
  264.  
  265.  
  266. ;;
  267. ;;
  268. ;;  Pixels :                          Scale Units :
  269. ;;
  270. ;; middle-length                   (- maximum minimum)
  271. ;;   
  272. ;;  [MAX]                               
  273. ;;    |                                 
  274. ;;    |                                 
  275. ;;    |              proportional       [MAX]
  276. ;;    |    -                              |
  277. ;;    |                                   |
  278. ;;    |   Pixel-delta                     |    -
  279. ;;    |                                   |    Scale-delta
  280. ;;    |                                   |  
  281. ;;  [MIN]  -                            [MIN]  -
  282. ;;  
  283. ;;    0                                 minimum
  284. ;;
  285. ;; Since :
  286. ;;
  287. ;; pixel-delta / middle-length = scale-delta / (- maximum minimum)
  288. ;;
  289. ;; Then :
  290. ;;
  291. ;; Pixel-delta = (* scale-delta middle-length) / (- maximum minimum)
  292. ;;
  293. ;; And :
  294. ;; 
  295. ;; Scale-delta = (* pixel-delta (- maximum minimum)) / middle-length
  296. ;;
  297. (defun units-to-pixels (slider scale-delta)
  298.   ;; Convert a scale delta to a pixel delta
  299.   (with-slots (minimum maximum middle-length) slider
  300.     (pixel-round (/ (* scale-delta middle-length)
  301.           (- maximum minimum)))))
  302.  
  303. (defun pixels-to-units (slider pixel-delta)
  304.   (with-slots (orientation minimum maximum increment middle-length) slider
  305.     ;; Convert a pixel delta to a scale units delta
  306.     ;; *DON'T* round this - units may be fractional !
  307.     (/ (* pixel-delta (- maximum minimum))
  308.        middle-length)))
  309.  
  310. ;; NOTE: The functions named ????-x and below return values strictly for a :horizontal 
  311. ;; slider and the return values must be translated for a :vertical slider.
  312.  
  313. (defun first-tick-x (slider)
  314.   (+ (slider-margin slider :min)
  315.      (first-tick-offset slider)))
  316.  
  317.  
  318. (defun drag-box-center-x (slider &optional (scale-value (scale-value slider)))
  319.   (with-slots (minimum) slider
  320.     ;; Returns dead center of drag-box
  321.     (+ (first-tick-x slider)
  322.        ;; Must subtract minimum since minimum can be negative and is NOT always zero!
  323.        (units-to-pixels slider (value-length scale-value minimum)))))
  324.  
  325.  
  326. (defun drag-box-min-x (slider &optional (scale-value (scale-value slider)))
  327.   (with-slots (dimensions minimum maximum) slider
  328.     (let* ((drag-box-width (slidebar-drag-box-width dimensions))
  329.        (gap (slidebar-gap dimensions)))
  330.       (- (drag-box-center-x slider scale-value)
  331.      (floor drag-box-width 2)
  332.      gap ;; subtract whitespace gap around drag-box
  333.      ))))
  334.  
  335. (defun drag-box-position (slider &optional (scale-value (scale-value slider)))
  336.   (declare (values x y width height))
  337.   (with-slots (orientation ) slider
  338.     ;; Return values describing area of drag-box for SCALE-VALUE
  339.     (let* ((drag-min-edge (drag-box-min-x slider scale-value))
  340.        (drag-image (getf (getf *slider-drag-box-images* orientation)
  341.                  (contact-scale slider))))
  342.  
  343.       (if (eq orientation :horizontal)
  344.       (values drag-min-edge
  345.           (+ (slider-margin slider :top) *slider-default-margin*)
  346.           (image-width drag-image)
  347.           (image-height drag-image))
  348.       (values (+ (slider-margin slider :top) *slider-default-margin*)
  349.           (translate-x-to-y drag-min-edge (image-height drag-image) slider)
  350.           (image-width drag-image)
  351.           (image-height drag-image)))
  352.       )))
  353.  
  354.   
  355. (defmethod scale-update ((slider slider) &key value minimum maximum indicator-size increment)
  356.   ;; Called by (method initialize-instance :after (slider)) to do error checking, and by
  357.   ;; SETF methods for slots in arglist above, and by (setf scale-value) called to move slider.
  358.   (with-slots
  359.     ((current-val value)
  360.      (current-min minimum)
  361.      (current-max maximum)
  362.      (current-ind indicator-size)
  363.      (current-inc increment)
  364.      orientation min-text-width max-text-width font)
  365.     slider
  366.     (let ((old-val (and value current-val)) ;; old-value & flag that value was passed in.
  367.       (old-min (and minimum current-min))
  368.       (old-max (and maximum current-max))
  369.       (old-inc (and increment current-inc))
  370.       (old-ind (and indicator-size current-ind)))
  371.  
  372.       (setf minimum        (or minimum current-min)
  373.         maximum        (or maximum current-max)
  374.         value          (or value (confine-to current-val minimum maximum))
  375.         indicator-size (or indicator-size current-ind)
  376.         increment      (or increment current-inc))
  377.  
  378.       (assert (and (numberp minimum) (numberp maximum)
  379.            (< minimum maximum))
  380.           (minimum maximum)
  381.           "Minimum (~a) is not less than maximum (~a)."
  382.           minimum maximum)
  383.  
  384.       (assert (and (numberp value)
  385.            (<= minimum value maximum))
  386.           (value)
  387.           "Value (~a) must be in the range [~a, ~a]."
  388.           value minimum maximum)
  389.  
  390.       (assert (or (eq :off indicator-size)
  391.           (and (numberp indicator-size)
  392.                (not (minusp indicator-size))))
  393.           (indicator-size)
  394.           "Indicator-size (~a) must be :OFF, 0, or a positive number."
  395.           indicator-size)
  396.  
  397.       (assert (and (numberp increment)
  398.            (< 0 increment (1+ (- maximum minimum))) ;; allow fractional increments, allow increment = maximum
  399.            (zerop (mod (- maximum minimum) increment)))
  400.           (increment)
  401.           "Increment (~a) must be in the range [0 ~a] and a factor of ~:*~d."
  402.           increment (- maximum minimum))
  403.  
  404.       ;;  Once VALUE & INCREMENT are valid we can align VALUE, if necessary,
  405.       ;;  to be a multiple of INCREMENT.
  406.       (setq value (+ minimum (align (value-length value minimum) increment)))
  407.       
  408.       (setf current-min minimum
  409.         current-max maximum
  410.         current-val value
  411.         current-ind indicator-size
  412.         current-inc increment
  413.         min-text-width (text-extents font (format nil "~a" minimum))
  414.         max-text-width (text-extents font (format nil "~a" maximum)))
  415.  
  416.       ;; Redisplay drag-box and any changes
  417.       (when (realized-p slider)
  418.     (cond ((or (and old-min
  419.             (not (= old-min current-min)))
  420.            (and old-max
  421.             (not (= old-max current-max)))
  422.            (and old-ind
  423.             (not (eq old-ind current-ind)))
  424.            (and old-inc
  425.             (not (= old-inc current-inc))))
  426.            (clear-area slider :exposures-p t))
  427.           
  428.           ((and old-val ;; when called with NEW increment value 
  429.             (not (= old-val current-val))) ;; when something has changed
  430.            
  431.            ;; Compute area of old drag-box ( if any )
  432.            (multiple-value-bind (old-x old-y old-width old-height)
  433.            (drag-box-position slider old-val)
  434.          
  435.          ;; Compute area of new drag-box 
  436.          (multiple-value-bind (x y width height)
  437.              (drag-box-position slider current-val)
  438.            
  439.            ;; Merge areas to redisplay : new drag-box, bar between old & new,
  440.            ;; old drag-box ( if any ), & tick marks obscured by drag-box
  441.            (when old-val
  442.              (if (eq orientation :horizontal)          
  443.              (setf width (+ (abs (- x old-x)) (max old-width width))
  444.                    x (min x old-x))
  445.              (setf height (+ (abs (- y old-y)) (max old-height height))
  446.                    y (min y old-y))))
  447.            (clear-area slider :x x :y y :width width :height height)
  448.            (display slider x y width height))))
  449.           (t)))
  450.       )))
  451.  
  452.  
  453. ;;;----------------------------------------------------------------------------+
  454. ;;;                                                                            |
  455. ;;;                            Initialization                                  |
  456. ;;;                                                                            |
  457. ;;;----------------------------------------------------------------------------+
  458.  
  459. (defun make-slider (&rest initargs &key &allow-other-keys)
  460.   (apply #'make-contact 'slider initargs))
  461.  
  462. (defun bar-bottom-offset (slider)
  463.   ;; Offset from the top (left) of horizontal (vertical) slider
  464.   ;; ... does NOT include (slider-margin slider :top) ...
  465.   ;; and 1-pixel past the bottom (right) edge of the slidebar
  466.   (with-slots (dimensions) slider
  467.     (+ *slider-default-margin*
  468.        (slidebar-gap dimensions)
  469.        (slidebar-bar-drag-offset dimensions)
  470.        (slidebar-bar-thickness dimensions))))
  471.  
  472. (defun fixed-thickness (slider &key include-text-p)
  473.   ;; The minimum thickness of slider required for the
  474.   ;; scale, orientation, and string characteristics of the minimum & maximum
  475.   (with-slots (orientation min-text-width max-text-width
  476.              indicator-size dimensions font) slider
  477.     (let ((x (first  (getf (slidebar-bar-text-offset dimensions) orientation)))
  478.       (y (second (getf (slidebar-bar-text-offset dimensions) orientation)))
  479.       (scale (contact-scale slider)))
  480.       (if (eq :off indicator-size)
  481.       (+ *slider-default-margin*
  482.          (if (eq orientation :horizontal)
  483.          (image-height (getf (getf *slider-drag-box-images* orientation) scale))
  484.          (image-width  (getf (getf *slider-drag-box-images* orientation) scale)))
  485.          *slider-default-margin*) ;; no space allocated for tick marks & text
  486.       ;; else
  487.       (if (eq orientation :horizontal)
  488.           (+ (bar-bottom-offset slider) y 
  489.          (if include-text-p
  490.              (+ (max-char-descent font) *slider-default-margin*)
  491.              0))
  492.           (+ (bar-bottom-offset slider) x 
  493.          (if include-text-p
  494.              (+ (max min-text-width max-text-width) *slider-default-margin*)
  495.              0)))))))
  496.  
  497. (defun slider-compute-margins (slider)
  498.   ;;  Now margins can be computed from the delta between the size needed and the
  499.   ;;  size we were given. The length of the slider basically stretches to fit but
  500.   ;;  any extra height results in the slider being centered in space provided.
  501.   ;;  PREFERRED-SIZE (via initialize-instance :after) MUST have been called to
  502.   ;;  set WIDTH & HEIGHT by this time.
  503.   (with-slots (orientation width height middle-length) slider
  504.     (let* ((total-min-thickness (fixed-thickness slider :include-text-p t))
  505.        (size          (if (eq orientation :horizontal) height width))
  506.        (top-margin    (floor (- size total-min-thickness) 2))
  507.        (bottom-margin (- size total-min-thickness top-margin)))
  508.       
  509.       (setf (getf (getf (window-plist slider) :slider-info) :margins)
  510.         ;; left top bottom right (horizontal)
  511.         (list :min 0 :top top-margin :text bottom-margin :max 0))
  512.  
  513.       ;; With margins set we can now compute and save middle-length for efficiency
  514.       (setf middle-length 
  515.         (- (if (eq :horizontal orientation)
  516.            width
  517.            height)
  518.            (slider-margin slider :min)
  519.            (first-tick-offset slider)
  520.            (last-tick-offset slider)
  521.            (slider-margin slider :max)
  522.            )))))
  523.  
  524.  
  525. (defmethod initialize-instance :after ((slider slider) &key &allow-other-keys)
  526.   (with-slots (font width height minimum maximum
  527.             dimensions min-text-width max-text-width) slider
  528.  
  529.     (setq font (find-font slider *default-display-text-font*)
  530.       dimensions (getf *slider-dimensions* (contact-scale slider)))
  531.     
  532.     (scale-update slider) ;; do some error checking, set min-text-width, etc.
  533.  
  534.     ;;  Initialize required geometry                 
  535.     (multiple-value-setq (width height) (preferred-size slider))
  536.     
  537.     ;; Compute margins now that WIDTH & HEIGHT are known
  538.     (slider-compute-margins slider)
  539.     ))
  540.  
  541.  
  542. ;;;----------------------------------------------------------------------------+
  543. ;;;                                                                            |
  544. ;;;                        Geometry Management                                 |
  545. ;;;                                                                            |
  546. ;;;----------------------------------------------------------------------------+
  547.  
  548.  
  549. (DEFMETHOD rescale :before ((slider slider))
  550.   (with-slots (font dimensions) slider
  551.     (setf font (find-font slider  *default-display-text-font*)
  552.       dimensions (getf *slider-dimensions* (contact-scale slider)))
  553.     (slider-compute-margins slider)
  554.     ))
  555.  
  556.  
  557. (defmethod resize :after ((slider slider) new-width new-height new-border-width)
  558.   ;; This method duplicates calculations started in (method initialize-instance :after (slider))
  559.   ;; but are done here since they also must be performed when change-geometry is invoked.
  560.   ;; Called when window-manager or someone else calls change-geometry.
  561.   (declare (ignore new-width new-height new-border-width))
  562.   (slider-compute-margins slider))
  563.  
  564.  
  565. (defmethod preferred-size ((slider slider) &key width height border-width)
  566.   (declare (ignore border-width)) ;; preferred-border-width is 0
  567.   (with-slots (orientation min-text-width max-text-width font dimensions indicator-size
  568.                (current-height height) (current-width width)) slider
  569.     (let* ((drag-box-width   (slidebar-drag-box-width dimensions))
  570.        (tick-mark-offset (slidebar-tick-mark-offset dimensions))
  571.  
  572.        ;;  Min width  of slider with 2 positions = double size of drag box 
  573.        (minimum-double-width
  574.          (+ (- (first-tick-offset slider) tick-mark-offset)
  575.         (max (* 2 drag-box-width)
  576.              (if (eq :off indicator-size)
  577.              0
  578.              (if (eq orientation :horizontal)
  579.                  ;; room needed to display text between first/last-tick
  580.                  (+ (ceiling min-text-width 2)
  581.                 (max-char-descent font) 
  582.                 (floor max-text-width 2))
  583.                  ;; room needed to display 2 text lines (min & max) vertically,
  584.                  ;; plus a small gap between
  585.                  (+ (max-char-ascent font)
  586.                 (max-char-descent font) ;; gap between
  587.                 (max-char-ascent font)))))
  588.         (- (last-tick-offset slider) tick-mark-offset)))
  589.        
  590.        ;; Calculate geometry assuming :horizontal orientation
  591.        (preferred-height
  592.          (max
  593.            ;; Suggested or current height
  594.            (if (eq orientation :horizontal)
  595.            (or height current-height)
  596.            (or width current-width))
  597.  
  598.            ;; Total thickness of horizontal bar
  599.            (fixed-thickness slider :include-text-p t)))
  600.          
  601.        (preferred-width
  602.          (max
  603.            ;; Suggested or current width
  604.            (if (eq orientation :horizontal)
  605.            (or width current-width)
  606.            (or height current-height))
  607.  
  608.            minimum-double-width))
  609.        )
  610.  
  611.       ;; Return preferred geometry according to actual orientation
  612.       (if (eq orientation :horizontal) ;; preferred-border-width is always 0
  613.       (values preferred-width preferred-height 0)
  614.       (values preferred-height preferred-width 0))
  615.     )))
  616.  
  617. ;;;----------------------------------------------------------------------------+
  618. ;;;                                                                            |
  619. ;;;                          Event Translations                                |
  620. ;;;                                                                            |
  621. ;;;----------------------------------------------------------------------------+
  622.  
  623. (defevent slider (:button-press :button-1)   slider-press)
  624. (defevent slider (:button-release :button-1) slider-release)
  625. (defevent slider (:motion-notify :button-1)  slider-handle-motion)
  626.  
  627. (defun slider-release (slider)
  628.   (declare (special *slider-pressed-p*))
  629.   (when (boundp '*slider-pressed-p*)
  630.     (throw-action slider :release t)))
  631.  
  632. (defun highlite-drag-box (slider gc x y width height gap)
  633.   (draw-rectangle slider gc (+ gap 1 gap x) (+ gap 1 gap y)
  634.           (- width (* 4 gap) 3) (- height (* 4 gap) 3) :fill-p))
  635.  
  636. (defun slider-press (slider)
  637.   (with-event (x y)
  638.     
  639.     (with-slots
  640.       (foreground orientation update-delay minimum maximum
  641.        increment width height display value dimensions)
  642.       slider
  643.  
  644.       (let (*slider-pressed-p* )
  645.     (declare (special *slider-pressed-p*))
  646.            
  647.     (multiple-value-bind (drag-x drag-y drag-width drag-height)
  648.       (drag-box-position slider)
  649.     
  650.     (when
  651.       (cond
  652.         ((and (>= x drag-x) (< x (+ drag-x drag-width))
  653.           (>= y drag-y) (< y (+ drag-y drag-height)))
  654.          
  655.          ;; SELECT on drag box
  656.          (let ((*highlight-pixel* (logxor foreground (contact-current-background-pixel slider)))
  657.            (gap (slidebar-gap dimensions)))
  658.            (declare (special *highlight-pixel*)) ;; use this in display method while moving ..
  659.            (using-gcontext
  660.          (gc :drawable slider
  661.              :function boole-xor
  662.              :foreground *highlight-pixel*)
  663.          
  664.          ;; Highlight drag area
  665.          (highlite-drag-box slider gc drag-x drag-y drag-width drag-height gap)  
  666.          
  667.          ;; Set timer for update
  668.          (when (and (numberp update-delay) (plusp update-delay))
  669.            (add-timer slider :update-delay update-delay))
  670.          
  671.          (apply-callback slider :begin-continuous)
  672.          (catch :release
  673.            (let ((*previous-position* (if (eq :vertical orientation) y x)))
  674.              (declare (special *previous-position*))
  675.              (loop (process-next-event display))))
  676.          (apply-callback slider :end-continuous)
  677.          
  678.          ;; Unhighlight drag area.
  679.          (multiple-value-bind (new-drag-x new-drag-y)
  680.              (drag-box-position slider)
  681.            (highlite-drag-box slider gc new-drag-x new-drag-y drag-width drag-height gap))))
  682.          t)
  683.  
  684.       ;;  SELECT on bar
  685.       ;;  Since it is NOT in drag-box, just check if it is in bar
  686.       ;;  or the area of the bar if it had the thickness of the drag-box.
  687.       ;;  This makes clicking somewhat easier.
  688.       ((multiple-value-bind (bar-x bar-y bar-width bar-height)
  689.            (if (eq orientation :horizontal)
  690.            (values (slider-margin slider :min) (slider-margin slider :top)
  691.                (- width (slider-margin slider :min) (slider-margin slider :max))
  692.                drag-height)
  693.            (values (slider-margin slider :top) (slider-margin slider :min)
  694.                drag-width
  695.                (- height (slider-margin slider :min) (slider-margin slider :max))))
  696.          (and (>= x bar-x) (>= y bar-y)
  697.           (< x (+ bar-x bar-width))
  698.           (< y (+ bar-y bar-height))))
  699.  
  700.        ;; Advance drag-box one increment in direction indicated.
  701.        ;; User may click so fast that the drag box passes the click
  702.        ;; position, thus inadvertently reversing the increment direction.
  703.        ;; Synchronize by using current pointer position, not click position.
  704.        (multiple-value-bind (ptr-x ptr-y) (pointer-position slider)
  705.          
  706.          (let ((delta (if (if (eq orientation :horizontal)
  707.                   (< ptr-x drag-x)
  708.                   (>= ptr-y (+ drag-y drag-height))) 
  709.                   (- increment)
  710.                   increment))        
  711.            (gap   (slidebar-gap dimensions)))
  712.            
  713.            (slider-increment-value slider delta)
  714.            
  715.            ;; Must warp pointer to stay in MIN (or MAX) bar, if necessary
  716.            (multiple-value-bind (new-drag-x new-drag-y drag-width drag-height)
  717.            (drag-box-position slider)
  718.          
  719.          (multiple-value-bind (warp-x warp-y)
  720.              (if (eq orientation :horizontal)
  721.              (if (plusp delta)
  722.                  (let ((min-x (min (1- width) (+ new-drag-x drag-width gap))))
  723.                    (when (< ptr-x min-x)
  724.                  (values min-x ptr-y)))
  725.                  (let ((max-x (max 0 (- new-drag-x gap))))
  726.                    (when (< max-x ptr-x)
  727.                  (values max-x ptr-y))))
  728.              
  729.              (if (minusp delta)
  730.                  (let ((min-y (min (1- height) (+ new-drag-y drag-height gap))))
  731.                    (when (< ptr-y min-y)
  732.                  (values ptr-x min-y)))
  733.                  (let ((max-y (max 0 (- new-drag-y gap))))
  734.                    (when (< max-y ptr-y)
  735.                  (values ptr-x max-y)))))
  736.            (when warp-x
  737.              (warp-pointer slider warp-x warp-y))))))
  738.        t))
  739.  
  740.       ;; Report final value, if necessary                       
  741.       (unless (eql 0 update-delay)
  742.         (delete-timer slider :update-delay)
  743.         (apply-callback slider :new-value value))))))))
  744.  
  745.  
  746.  
  747. (defun slider-increment-value (slider scale-increment)
  748.   "Convert the scale-increment to a (possibly) new scale position
  749. and (possibly) cause the slider to be updated."
  750.  
  751.   (with-slots (value orientation increment minimum maximum update-delay) slider
  752.     ;;  Must use truncate for negative scale-increment's - rounds to zero.
  753.     (let* ((new-value (+ value scale-increment))
  754.        (adjusted  (confine-to (or (apply-callback slider :adjust-value new-value)
  755.                       new-value)
  756.                   minimum maximum)))
  757.  
  758.       (unless (= value adjusted)   ;; unless no change in slider scale occurs
  759.  
  760.     (setf (scale-value slider) adjusted) ;; <- this calls scale-update & redisplays slider
  761.  
  762.     (when (eql 0 update-delay)
  763.       (apply-callback slider :new-value adjusted))))))
  764.  
  765.  
  766. (defun slider-handle-motion (slider)
  767.   (declare (special *previous-position*))
  768.   (when (boundp '*previous-position*)
  769.     (with-slots (orientation increment) slider   
  770.       (with-event (state x y)       
  771.     (multiple-value-bind (ptr-x ptr-y)
  772.         ;;  Is :button-1 still down?
  773.         (if (plusp (logand state #.(make-state-mask :button-1)))
  774.         
  775.         ;; Yes, query current pointer position
  776.         (pointer-position slider)
  777.         
  778.         ;; No, use final x,y returned for button transition
  779.         (values x y))
  780.       
  781.       (let
  782.         ((modulo-increment
  783.            (* (truncate
  784.             (pixels-to-units
  785.               slider
  786.               (if (eq :horizontal orientation)
  787.               (- ptr-x *previous-position*)
  788.               
  789.               ;; Must swap order of subtraction since positive y direction
  790.               ;; is negative scale direction for :vertical slider
  791.               (- *previous-position* ptr-y)))
  792.             increment)
  793.           increment)))
  794.         
  795.         ;;  Convert the pixel motion to a suitable slider scale motion
  796.         (unless (zerop modulo-increment)
  797.           (slider-increment-value slider modulo-increment)
  798.           ;;  Use drag-box position. Ptr position is only correct if the drag-box can
  799.           ;;  move to the ptr posiiton without bumping up against the min/max limits.
  800.           (setf *previous-position*
  801.             (if (eq orientation :horizontal)
  802.             (drag-box-center-x slider)
  803.             (translate-x-to-y (drag-box-center-x slider) 1 slider))
  804.             ))))))))
  805.  
  806. (defun choose-indicator-size (slider)
  807.   "Returns TICK-LIMIT = the number of ticks to draw."
  808.   ;; Called when indicator-size eq :off to automatic tick-marks
  809.   (declare (values tick-limit increments-in-tick))
  810.  
  811.   (with-slots (maximum minimum increment) slider
  812.     (let* ((tick-mark-thickness (slider-tick-mark-thickness slider))
  813.        (increments-in-tick    1)
  814.        (min-visible-width   (* 2 tick-mark-thickness))
  815.        (ticks               (floor (- maximum minimum) increment))
  816.        )
  817.       ;;  Return appropriate tick-limit
  818.       (values
  819.     (1+     ;; 1+ since we draw the first-tick plus any calculated ticks
  820.       (do* ((ticks-visible nil))
  821.            ((cond
  822.           ((<= ticks 1) (setq ticks-visible 1)) ;; reached minimum ticks, 1 at each end
  823.           ((>= (units-to-pixels slider (* increments-in-tick increment))
  824.                min-visible-width)
  825.            (setq ticks-visible ticks)))
  826.         ;; Exit form
  827.         (return ticks-visible))
  828.               
  829.         (setq ticks (floor ticks 2)
  830.           increments-in-tick (* 2 increments-in-tick))))
  831.  
  832.     increments-in-tick ;; 2nd return value
  833.     )))) 
  834.  
  835. ;;;----------------------------------------------------------------------------+
  836. ;;;                                                                            |
  837. ;;;                               Display                                      |
  838. ;;;                                                                            |
  839. ;;;----------------------------------------------------------------------------+
  840. ;;; d1 = first-tick-x 
  841. ;;; d2 = slidebar-tick-mark-offset 
  842. ;;; 
  843. ;;;     min  fill  drag   empty   max
  844. ;;; +-----------------------------------+ -
  845. ;;; |                                   |  gap
  846. ;;; |             +----+                | -
  847. ;;; |             |   ||                |  drag-bar-offset 
  848. ;;; |   .-- ----- |   || -------- --.   | -
  849. ;;; |   |** ***** |   ||            |   |  bar-thickness
  850. ;;; |   `-- ----- |   || -------- --'   | -
  851. ;;; |             |---+|                |
  852. ;;; |<----->      +----+                |
  853. ;;; | d1  ||        ||           ||     |  bar-text-offset    
  854. ;;; |   <-->                            |
  855. ;;; |     d2                            |
  856. ;;; |    MIN                     MAX    |
  857. ;;; +-----------------------------------+ -
  858. ;;;     ^^ ^     ^       ^       ^^ ^
  859. ;;;     || |     |       |       || +-- bar-max-x
  860. ;;;     || |     |       |       |+----   (<= last-tick-x MAX-UPPER-EDGE bar-max-x)
  861. ;;;     || |     |       |       +----- last-tick-x
  862. ;;;     || |     |       +------------- drag-max-edge
  863. ;;;     || |     +--------------------- drag-min-edge
  864. ;;;     || +--------------------------- first-tick-x
  865. ;;;     |+-----------------------------   (<= bar-min-x MIN-LOWER-EDGE first-tick-x )
  866. ;;;     +------------------------------ bar-min-x
  867. ;;; 
  868. ;;; Note: No margins are shown except *slider-default-margin* 
  869. ;;;
  870. ;;;
  871. ;;;    .--.
  872. ;;;    |  |   max
  873. ;;;
  874. ;;;    |  |
  875. ;;;    |  |
  876. ;;;    |  |   empty
  877. ;;;    |  |
  878. ;;;
  879. ;;;   +----+
  880. ;;;   |    |
  881. ;;;   |    |  drag
  882. ;;;   |    |
  883. ;;;   +----+
  884. ;;;
  885. ;;;    |**|
  886. ;;;    |**|   fill
  887. ;;;    |**|
  888. ;;;    |**|
  889. ;;;
  890. ;;;    |**|   min
  891. ;;;    `--'
  892. ;;;
  893.   
  894. (defmethod display ((slider slider) &optional at-x at-y at-width at-height &key) 
  895.   (with-slots (dimensions width height foreground orientation
  896.               minimum maximum increment middle-length sensitive
  897.               min-text-width max-text-width indicator-size font) slider
  898.     ;; Default exposed rectangle, if necessary
  899.     (setf at-x      (or at-x      0)
  900.       at-y      (or at-y      0)
  901.       at-width  (or at-width  (- width at-x))
  902.       at-height (or at-height (- height at-y)))
  903.     (let* ((drag-box-width  (slidebar-drag-box-width dimensions))
  904.        (drag-bar-offset (slidebar-bar-drag-offset dimensions))
  905.        (gap         (slidebar-gap dimensions))
  906.        (bar-thickness   (slidebar-bar-thickness dimensions))
  907.        (bar-image       (getf (getf *slider-bar-images* :masks) (contact-scale slider)))
  908.        (image-half-size (floor (image-width bar-image) 2))  ;; image is BOTH ends, use min half
  909.        (first-tick-x    (first-tick-x slider))
  910.        (last-tick-x     (+ first-tick-x middle-length))
  911.        (bar-y           (+ (slider-margin slider :top) *slider-default-margin*
  912.                    gap drag-bar-offset))
  913.        (bar-min-x       (- first-tick-x ;; ZRP
  914.                    (slidebar-tick-mark-offset dimensions)))
  915.        (bar-max-x       (+ last-tick-x (slidebar-tick-mark-offset dimensions)))
  916.        (drag-min-edge   (drag-box-min-x slider))
  917.        (drag-max-edge   (+ drag-min-edge gap drag-box-width gap))
  918.        (end-portion     (min image-half-size (max 0 (- drag-min-edge bar-min-x)))) ;; for min end ONLY
  919.        (min-lower-edge  (+ bar-min-x end-portion))
  920.        (max-upper-edge  (max (- bar-max-x image-half-size) drag-max-edge))
  921.        (mask            (contact-image-mask slider bar-image
  922.                         :foreground foreground
  923.                         :background (contact-current-background-pixel slider)))
  924.        (inactive-p      (not (sensitive-p slider)))
  925.        (scale         (contact-scale slider))
  926.        )
  927.  
  928.       ;; First draw the bar outline, then
  929.       ;; draw the tick-marks, draw the tick-text, and
  930.       ;; finally, fill the bar then blt the drag-box to the correct position.
  931.       (using-gcontext (gc :drawable slider
  932.               :font font
  933.               :exposures :off
  934.               :foreground (if inactive-p
  935.                       (logxor foreground (contact-current-background-pixel slider))
  936.                       foreground)
  937.               :fill-style (when inactive-p :stippled)
  938.               :function   (when inactive-p boole-xor)
  939.               ;; Use 50%gray, since 25%gray looks bad (bar disappears) for args :
  940.               ;; (make-slider :width 200 :height 200 :maximum 4 :orientation :vertical :scale :medium)
  941.               :stipple    (when inactive-p (contact-image-mask slider 50%gray :depth 1))
  942.               :clip-mask  (list at-x at-y at-width at-height)
  943.               )
  944.     ;; Draw MIN end - if it will be visible after drag-box is drawn later.
  945.     ;; The zero reference point (ZRP) is the center of the first tick-mark.
  946.     ;; If at this position we just draw the drag-box at ZRP after subtracting
  947.     ;; the half-width of the drag-box to get the coordinate of the left edge.
  948.     ;; Actually the image blt to the slider also contains a gap, but the ZRP is
  949.     ;; situated such that it centers the drag-box at the extreme min position.
  950.     
  951.     ;;  Draw (at least part of) MIN
  952.     (when (> drag-min-edge bar-min-x) ;; drag-box is NOT less than gap away from MIN edge
  953.       (multiple-value-bind (src-x src-y -width -height dst-x dst-y)
  954.           (if (eq orientation :horizontal)
  955.           (values 0 0 end-portion (image-width bar-image) bar-min-x bar-y)
  956.           (values 0 (- (image-width bar-image) image-half-size) ;; image may have odd # of pixels
  957.               (image-width bar-image) end-portion
  958.               bar-y (translate-x-to-y bar-min-x end-portion slider)))
  959.         (when (area-overlaps-p at-x at-y at-width at-height dst-x dst-y -width -height)
  960.           (if inactive-p
  961.           (draw-rectangle slider gc dst-x dst-y -width -height :fill-p)
  962.           (copy-area mask gc src-x src-y -width -height slider dst-x dst-y))))
  963.             
  964.       ;; Draw FILL, if any
  965.       (when (> drag-min-edge min-lower-edge)
  966.         (multiple-value-bind (x y -width -height)
  967.         (if (eq orientation :horizontal)
  968.             (values min-lower-edge bar-y (- drag-min-edge min-lower-edge) bar-thickness)
  969.             (values bar-y (translate-x-to-y min-lower-edge (- drag-min-edge min-lower-edge) slider)
  970.                 bar-thickness (- drag-min-edge min-lower-edge)))
  971.         (when (area-overlaps-p at-x at-y at-width at-height x y -width -height)
  972.           (draw-rectangle slider gc x y -width -height :fill-p))))
  973.         )
  974.     
  975.     ;; Draw EMPTY portion, if any
  976.     (when (> max-upper-edge drag-max-edge)
  977.       (multiple-value-bind (x y x2 y2 x3 y3 x4 y4)
  978.           (if (eq orientation :horizontal)
  979.           (values drag-max-edge bar-y                     ;; x y
  980.               max-upper-edge bar-y                    ;; x2 y2
  981.               drag-max-edge (+ bar-y bar-thickness -1)        ;; x3 y3
  982.               max-upper-edge (+ bar-y bar-thickness -1))        ;; x4 y4
  983.           (values bar-y (translate-x-to-y drag-max-edge 1 slider)    ;; x y
  984.               bar-y (translate-x-to-y max-upper-edge 1 slider)    ;; x2 y2
  985.               (+ bar-y bar-thickness -1) (translate-x-to-y drag-max-edge 1 slider)         ;; x3 y3
  986.               (+ bar-y bar-thickness -1) (translate-x-to-y max-upper-edge 1 slider)))    ;; x4 y4
  987.         (when (area-overlaps-p at-x at-y at-width at-height x y (- x2 x) bar-thickness)
  988.           (draw-segments slider gc (list x y x2 y2 x3 y3 x4 y4))))
  989.       )
  990.  
  991.       ;; Draw MAX, or portion not obscured by drag-box
  992.     (when (plusp (setq end-portion (min image-half-size (- bar-max-x max-upper-edge))))
  993.       (setq mask (contact-image-mask slider (getf (GETF *slider-bar-images* :borders) scale)
  994.                      :foreground foreground
  995.                      :background (contact-current-background-pixel slider)))
  996.       (multiple-value-bind (src-x src-y -width -height dst-x dst-y)
  997.           (if (eq orientation :horizontal)
  998.           (values (- (image-width bar-image) end-portion) 0
  999.               end-portion (image-width bar-image)
  1000.               max-upper-edge bar-y)
  1001.           (values 0 0
  1002.               (image-width bar-image) end-portion
  1003.               bar-y (translate-x-to-y max-upper-edge end-portion slider)))
  1004.         (when (area-overlaps-p at-x at-y at-width at-height dst-x dst-y -width -height)
  1005.           (if inactive-p
  1006.           (draw-rectangle slider gc dst-x dst-y -width -height :fill-p)
  1007.           (copy-area mask gc src-x src-y -width -height slider dst-x dst-y))))
  1008.       )
  1009.  
  1010.     ;; Draw TICK-TEXT, the labels for MIN and MAX
  1011.     (unless (eq :off indicator-size) ;; don't draw tick marks or tick text
  1012.  
  1013.       (let ((min-thickness (+ (slider-margin slider :top)
  1014.                   (fixed-thickness slider :include-text-p nil)))
  1015.         (text-x-offset (first  (getf (slidebar-bar-text-offset dimensions) orientation)))
  1016.         (text-y-offset (second (getf (slidebar-bar-text-offset dimensions) orientation))))
  1017.         
  1018.         (multiple-value-bind (x-min y-min x-max y-max)
  1019.         (if (eq orientation :horizontal)
  1020.             (values (+ (slider-margin slider :min)
  1021.                    text-x-offset
  1022.                    ;; center text at :first-tick
  1023.                    (+ (first-tick-offset slider)
  1024.                   (- (floor min-text-width 2))))
  1025.                 min-thickness
  1026.                 (- width
  1027.                    (slider-margin slider :max)
  1028.                    (last-tick-offset slider)
  1029.                    (ceiling max-text-width 2))  ;; scoot left to fit on odd widths!
  1030.                 min-thickness)
  1031.             (values min-thickness
  1032.                 (+ (slider-margin slider :max)
  1033.                    (last-tick-offset slider)
  1034.                    middle-length
  1035.                    (slidebar-tick-mark-offset dimensions)
  1036.                    (- gap))
  1037.                 min-thickness
  1038.                 (+ (slider-margin slider :max)
  1039.                    (last-tick-offset slider)
  1040.                    (- (slidebar-tick-mark-offset dimensions))
  1041.                    text-y-offset)))
  1042.           (let* ((font-ascent (max-char-ascent font))
  1043.              (font-height (+ font-ascent (max-char-descent font))))
  1044.         (when (if (eq orientation :horizontal)
  1045.               (area-overlaps-p at-x at-y at-width at-height
  1046.                        x-min (- y-min font-ascent)
  1047.                        (+ (- x-max x-min) max-text-width)
  1048.                        font-height)
  1049.               (area-overlaps-p at-x at-y at-width at-height
  1050.                        x-max (- y-max font-ascent)
  1051.                        (max min-text-width max-text-width)
  1052.                        (+ (- y-min y-max) font-height)))
  1053.           ;; Draw TICK-TEXT for min and max        
  1054.           (draw-glyphs slider gc x-min y-min (format nil "~a" minimum))
  1055.           (draw-glyphs slider gc x-max y-max (format nil "~a" maximum))))))
  1056.       
  1057.       ;; Draw TICK-MARKS
  1058.       (multiple-value-bind (tick-limit increments-in-tick)
  1059.           (if (plusp indicator-size)
  1060.           (values (1+ (floor (- maximum minimum) (* increment indicator-size)))
  1061.               indicator-size)
  1062.           (choose-indicator-size slider)) ;; automatic tick marks
  1063.         (do* ((tick 0 (incf tick))
  1064.           (tick-thickness (slider-tick-mark-thickness slider))
  1065.           (tick-x (+ (first-tick-x slider) (- (floor tick-thickness 2))) ;; adjust from center to edge of tick
  1066.               (+ (first-tick-x slider) (- (floor tick-thickness 2))
  1067.                  (units-to-pixels slider (* tick increments-in-tick increment))))
  1068.           (tick-y (+ (slider-margin slider :top)
  1069.                  (bar-bottom-offset slider)
  1070.                  (slider-bar-tick-gap slider)))
  1071.           (tick-height (slidebar-tick-mark-length dimensions)))
  1072.          
  1073.          ((= tick tick-limit)) ;; draw tick @min plus TICK-LIMIT more
  1074.  
  1075.           (multiple-value-bind (x y -width -height)
  1076.           (if (eq orientation :horizontal)
  1077.               (values tick-x tick-y
  1078.                   tick-thickness tick-height)
  1079.               (values tick-y (translate-x-to-y tick-x tick-thickness slider)
  1080.                   tick-height tick-thickness))
  1081.         (when (area-overlaps-p at-x at-y at-width at-height x y -width -height)
  1082.           (draw-rectangle slider gc x y -width -height :fill-p)
  1083.           ))))
  1084.       )
  1085.  
  1086.     ;; Draw DRAG BOX (possibly over a tick mark)
  1087.     (let ((drag-image (getf (getf *slider-drag-box-images* orientation) scale)))
  1088.       (setq mask (contact-image-mask slider drag-image 
  1089.                      :foreground foreground
  1090.                      :background (contact-current-background-pixel slider)))
  1091.       (multiple-value-bind (src-x src-y -width -height dst-x dst-y)
  1092.           (if (eq orientation :horizontal)
  1093.           (values 0 0 (image-width drag-image) (image-height drag-image)
  1094.               drag-min-edge (- bar-y drag-bar-offset gap))
  1095.           (values 0 0 (image-width drag-image) (image-height drag-image)
  1096.               (- bar-y drag-bar-offset gap)
  1097.               (translate-x-to-y drag-min-edge (image-height drag-image) slider)))
  1098.         (when (area-overlaps-p at-x at-y at-width at-height
  1099.                    dst-x dst-y -width -height)
  1100.  
  1101.           (if inactive-p
  1102.           (draw-rectangle slider gc dst-x dst-y -width -height :fill-p)
  1103.           (copy-area mask gc src-x src-y -width -height slider dst-x dst-y))
  1104.           (when (boundp '*highlight-pixel*)
  1105.         (special-highlite-drag-box slider gc dst-x dst-y -width -height gap)))))))))
  1106.  
  1107. ;;; Crock! This function could be inlined, except that causes the Explorer compiler
  1108. ;;; to barf on (method display (slider)) when using R4 CLX. 
  1109. (defun special-highlite-drag-box (slider gc dst-x dst-y -width -height gap)
  1110.   (declare (special *highlight-pixel*))
  1111.   (with-gcontext (gc :function boole-xor :foreground *highlight-pixel*)
  1112.     ;; Highlight drag area while button is still down )
  1113.     (highlite-drag-box slider gc dst-x dst-y -width -height gap)))
  1114.  
  1115.